;***********************************************************************
; scheduler_ISR_64.asm
;
; provides IRQ handlers for both the AP APIC timers and the scheduler's HPET
;
; Author: Aidan Goddard 27/6/13
;
;***********************************************************************

section .text

global ISR_APIC_scheduler_timer
global ISR_HPET_scheduler_timer

global ChangePML4

extern HPETSleepCounterISR
extern APICSchedulerISR

extern Printf

;/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
;/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
; little function to change the PML4 value if it is different
; rdi = new PML4
ChangePML4:
	; check the cr3 value to see if it is the same, if so, don't bother updating it
	mov 	rax, cr3		; read current cr3
	cmp 	rax, rdi		; compare it with the new one
	je		.finish			; if they are equal, skip next
	mov 	cr3, rdi		; else, update cr3 with new value

.finish:
	ret


;/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
;/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
; APIC timer ISR
ISR_APIC_scheduler_timer:
	; first disable interrupts
	cli

	; save trashed registers
	push	rax
	push	rcx
	push	rdx
	push	rbp


	; save base pointer for loading registers
	mov 	rbp, rsp

	; check scheduling state
	mov 	rax, 0x100b00000 + 0x23a228		; load address of scheduling state variable
	mov		eax, [rax]						; get the value
	or		eax, eax						; check for zero
	jnz		.scheduling_enabled				; if non-zero, skip to scheduling enabled

		; scheduling is disabled
		; set CPU state to not scheduling
		str		rax								; get CPU TSS
		sub 	rax, 0x40						; subtract TSS offset
		shr 	rax, 4							; div by 16 to get OS-assigned CPU ID
		mov 	rcx, 0x100b00000 + 0x23a338		; get base of CPU states table
		mov		[rcx + rax * 4], DWORD 0		; set state to 0 (not scheduling)

		; check current thread
		; CPU ID is in rax
		mov 	rcx, 0x100b00000 + 0x23a238		; get base of CPU current thread pointers
		mov 	rcx, [rcx + rax * 8]			; get current thread
		mov		rax, 0x24b000 + 0x100b00000		; address of idle TCB
		or 		rcx, rcx						; check for 0 (idle thread)

		; if running idle thread, just finish
		jz		.finish_return

		; if not running idle thread, save state and then load idle thread
		mov 	rdx, 3
		jmp 	.get_thread_base


.scheduling_enabled:
	; scheduling is enabled (normal)
	; check if running the idle thread
	str		rax								; get CPU TSS
	sub 	rax, 0x40						; subtract TSS offset
	shr 	rax, 4							; div by 16 to get OS-assigned CPU ID
	mov 	rcx, 0x100b00000 + 0x23a238		; get base of CPU current thread pointers
	mov 	rcx, [rcx + rax * 8]			; get current thread
	or 		rcx, rcx						; check for 0 (idle thread)
	jnz		.not_idle_thread				; skip next bit if not running the idle thread

		; running idle thread
		; check front of active queue (lock not required for this peek) for task
		mov 	rdx, 1							; default scheduler entry mode (new task please, im currently idle)
		str		rax								; get CPU TSS
		sub 	rax, 0x40						; subtract TSS offset
		shr 	rax, 4							; div by 16 to get OS-assigned CPU ID
		mov 	rcx, 0x100b00000 + 0x23a000		; get base of active queue start table
		mov 	rcx, [rcx + rax * 8]			; get address of thread at front of queue
		or		rcx, rcx						; check for NULL (0)
		jnz		.call_scheduler					; direct call the scheduler if there is a task in the queue

		; otherwise, no task in the queue, carry on idling
		mov		rax, 0x24b000 + 0x100b00000		; address of idle TCB
		jmp		.finish_return

.not_idle_thread:
	; grab process state and thread state
	; rcx points to the current thread TLC
	movzx	rax, WORD [rcx]			; get the Pindex
	movzx	rcx, WORD [rcx + 2]		; get the Tindex
	shl		rax, 10					; multiply Pindex by 1024 to get the offset into the process's PCB
	mov  	rdx, 0x100b00308		; get base address of PCBs + offset into process state
	add 	rax, rdx				; get address of process state field for this process
	mov 	edx, [rax]				; get the process state into rdx

	mov 	rax, [rax + 0x10]				; get the address of this process's PDB
	mov 	eax, [rax + rcx * 4 + 0x400]	; get the current thread's state (rcx = TID, rax = base of PDB, 0x400 = offset for thread states)

	; rdx = process state
	; rax = thread state
	; check if process is terminating
	cmp 	rdx, 2
	jne		.not_terminating_process

		; process needs to be terminated
		; set scheduler entry mode and go to scheduler (don't need to save state if terminating)
		mov 	rdx, 7
		jmp 	.call_scheduler

.not_terminating_process:
	; check if thread is terminating
	; rax = thread state
	cmp 	rax, 2
	jne		.not_terminating

		; thread needs to be terminated
		; set scheduler entry mode and go to scheduler (don't need to save state if terminating)
		mov 	rdx, 5
		jmp		.call_scheduler

.not_terminating:
	; check if thread is waiting
	; rax = thread state
	cmp 	rax, 4
	jne		.not_waiting

		; thread needs to be made to wait
		; set scheduler entry mode and go to state save
		mov 	rdx, 6
		jmp		.get_thread_base

.not_waiting:
	; check if thread is sleeping
	; rax = thread state
	cmp 	rax, 3					; 3 is the value for a thread that needs to go to sleep
	jne		.not_sleeping			; if it is not 3, then skip next bit

		; thread needs to go to sleep
		; set scheduler entry mode and go to state save
		mov 	rdx, 4
		jmp 	.get_thread_base

.not_sleeping:
	; check front of active queue (lock not required for this peek) for task
	str		rax								; get CPU TSS
	sub 	rax, 0x40						; subtract TSS offset
	shr 	rax, 4							; div by 16 to get OS-assigned CPU ID

	mov 	rcx, 0x100b00000 + 0x23a000		; get base of active queue start table
	mov 	rcx, [rcx + rax * 8]			; get address of thread at front of queue
	or		rcx, rcx						; check for NULL (0)
	jz		.finish_return					; if zero, finish (no jobs available to switch to)
	mov 	rdx, 2							; set scheduling mode to 2 (new task please, im not idle)

.get_thread_base:
	; grabs the base address of the current thread structure
	; rbp = base of stack (rbp, rdx, rcx, rax, ISR frame)
	; rdx = scheduler mode when it's called after this (preserved)
	str		rax								; get CPU TSS
	sub 	rax, 0x40						; subtract TSS offset
	shr 	rax, 4							; div by 16 to get OS-assigned CPU ID

	mov 	rcx, 0x100b00000 + 0x23a238		; get base of CPU current thread pointers
	mov 	rcx, [rcx + rax * 8]			; get current thread TLC pointer
	push	rcx								; save it for later
	movzx 	rcx, WORD [rcx]					; get the Pindex
	shl		rcx, 10							; multiply by 1024
	mov 	rax, 0x100b00000 + 0x320		; add base of PCB and TCB address entry
	add 	rcx, rax
	mov 	rax, [rcx]						; get address of TCB base
	pop		rcx								; restore the TLC pointer
	movzx	rcx, WORD [rcx + 2]				; get the thread index
	shl		rcx, 12							; multiply by 4096
	add		rax, rcx						; add it to the TCB base address

.save_thread_state:
	; saves the thread state. SHOULD ONLY BE PERFORMED WHEN THE CURRENT THREAD IS NOT THE IDLE THREAD
	; rax = ptr to thread entry base
	; rbp = base of stack (rbp, rdx, rcx, rax, ISR frame)
	; rdx = scheduler mode when it's called after this (preserved)
	; first save fpu state
	fxsave	[rax]

	; then save stacked registers
	mov 	rcx, [rbp]
	mov 	[rax + 3160], rcx		; rbp
	mov 	rcx, [rbp + 8]
	mov 	[rax + 3136], rcx		; rdx
	mov 	rcx, [rbp + 16]
	mov 	[rax + 3128], rcx		; rcx
	mov 	rcx, [rbp + 24]
	mov 	[rax + 3112], rcx		; rax

	; then save ISR stack frame registers
	mov 	rcx, [rbp + 32]
	mov 	[rax + 3072], rcx		; rip
	mov 	rcx, [rbp + 40]
	mov 	[rax + 3088], rcx		; cs
	mov 	rcx, [rbp + 48]
	mov 	[rax + 3080], rcx		; rflags
	mov 	rcx, [rbp + 56]
	mov 	[rax + 3168], rcx		; rsp

	; then save non-stacked registers
	mov 	[rax + 3120], rbx
	mov 	[rax + 3176], r8
	mov 	[rax + 3184], r9
	mov 	[rax + 3192], r10
	mov 	[rax + 3200], r11
	mov 	[rax + 3208], r12
	mov 	[rax + 3216], r13
	mov 	[rax + 3224], r14
	mov 	[rax + 3232], r15

.call_scheduler:
	; saves trashed registers and calls the scheduler
	; rdx = scheduler mode
	; first save stack base pointer (for restore ops)
	push 	rbp

	; then save other trashed registers
	push	rdi
	push	rsi
	push	r8
	push	r9
	push	r10
	push	r11

	; then move rdx to rdi for C call
	mov 	rdi, rdx
	mov 	rax, APICSchedulerISR
	call 	rax

	; then restore the registers
	pop		r11
	pop		r10
	pop		r9
	pop		r8
	pop		rsi
	pop		rdi

.load_resources:
	; task selector returns here for resource load and return
	; cr3 is already loaded with correct PML4 (except when switching to the IDLE thread) and the required scheduling states have been set
	; just need to load the registers now
	; rax = ptr to thread data structure to load thread info from
	; restore pointer to base of stack
	pop		rbp

	; check if idle thread (fixed at 0x24b000 + 0x100b00000)
	; if idle thread, skip all but stack frame loading
	mov		rcx, 0x24b000 + 0x100b00000
	cmp 	rax, rcx
	jne		.not_loading_idle		; skip next bit if not loading idle thread

	; loading idle thread, check PML is different
	mov 	rcx, cr3				; get current PML4
	mov 	rdx, 0x100000			; load the idle/system thread PML4
	cmp 	rcx, rdx				; compare the 2 for equality
	je		.load_stack_frame		; if equal, means that idle/system PML4 is already in use
	mov 	cr3, rdx				; otherwise, it needs to be loaded into cr3
	jmp 	.load_stack_frame		; then skip to the stack frame load

.not_loading_idle:
	; perform floating point load
	fxrstor	[rax]

	; load the non-stacked registers
	mov 	rbx, [rax + 3120]
	mov 	r8, [rax + 3176]
	mov 	r9, [rax + 3184]
	mov 	r10, [rax + 3192]
	mov 	r11, [rax + 3200]
	mov 	r12, [rax + 3208]
	mov 	r13, [rax + 3216]
	mov 	r14, [rax + 3224]
	mov 	r15, [rax + 3232]

	; load the stacked registers
	mov 	rcx, [rax + 3160]		; rbp
	mov 	[rbp], rcx
	mov 	rcx, [rax + 3136]		; rdx
	mov 	[rbp + 8], rcx
	mov 	rcx, [rax + 3128]		; rcx
	mov 	[rbp + 16], rcx
	mov 	rcx, [rax + 3112]		; rax
	mov 	[rbp + 24], rcx

.load_stack_frame:
	; rax points to base of thread data structure
	; load the ISR stack frame (ss is always fixed)
	mov 	rcx, [rax + 3072]		; rip
	mov 	[rbp + 32], rcx
	mov 	rcx, [rax + 3088]		; cs
	mov 	[rbp + 40], rcx
	mov 	rcx, [rax + 3080]		; rflags
	mov 	[rbp + 48], rcx
	mov 	rcx, [rax + 3168]		; rsp
	mov 	[rbp + 56], rcx

.finish_return:
	; finished
	; perform CPU usage counting
	; first check if running the idle thread
	; rax = idle thread TCB
	mov		rcx, 0x24b000 + 0x100b00000		; address of idle TCB
	cmp		rax, rcx						; check if equal
	jne		.not_idle_time_keeping			; skip next bit if not running the idle thread

	; running idle thread
	; need to decrease the usage counter for this CPU
	str		rax								; get the CPU ID into rax
	sub		rax, 0x40
	shr		rax, 4
	mov 	rcx, 0x100b00000 + 0x23a4b8		; get base address of % usage counter
	lea 	rdx, [rcx + rax * 4]			; get offset for this CPU
	mov 	ecx, [rdx]
	dec		ecx
	mov 	[rdx], ecx

.not_idle_time_keeping:
	; decrease timer and check if zero.
	str		rax								; get the CPU ID into rax
	sub		rax, 0x40
	shr		rax, 4
	mov 	rcx, 0x100b00000 + 0x23a438		; get base address of % usage timer
	lea 	rdx, [rcx + rax * 4]			; get offset for this CPU
	mov 	ebp, [rdx]						; get value of the timer
	dec		ebp								; decrease it
	mov 	[rdx], ebp						; save it back
	or		ebp, ebp						; check it for zero
	jnz		.dont_reset_counter				; if it's not zero, skip next bit

	; timer is zero, need to reset things
	mov		[rdx], DWORD 100				; reset timer to 100
	mov		ecx, [rdx + 128]				; get counter value
	mov 	[rdx - 128], ecx				; save it to final count
	mov 	[rdx + 128], DWORD 100			; reset counter to 100

.dont_reset_counter:
	; send EOI to APIC
	mov		rcx, 0x1b
	rdmsr
	mov		rcx, 0xfffff000
	and		rax, rcx
	add		rax, 0xb0
	mov		[rax], DWORD 0

	; restore trashed registers
	pop		rbp
	pop		rdx
	pop		rcx
	pop		rax

	; return
	iretq

;/////////////////////////////////////////////////////////////////////////////////
.msgTest db 10,"0x%a",0


	mov 	rdi, .msgTest
	mov 	rsi, [rbp + 32]
	call 	Printf
	cli
	hlt

; function to save the register state to the thread area
; rax = ptr to thread save area base
; rbp = base of stack (rbp, rdx, rcx, rax, ISR frame)
; PML4 must be loaded for current process
; trashes rax, rcx, rdx, rbp

.save_state:

	; finished, return
	ret


;/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
;/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
; HPET timer ISR
ISR_HPET_scheduler_timer:
	; first disable interrupts
	cli

	; save trashed registers
	push	rax
	push	rcx
	push	rdx
	push	rsi
	push	rdi
	push	r8
	push	r9
	push	r10
	push	r11

	; get address of scheduler table sleeping queue start and check if the queue is empty
	mov 	rax, 0x100b00000 + 0x23a210			; load address of queue start ptr
	mov 	rcx, [rax]							; get address
	or		rcx, rcx							; check for null (0)
	jz		.sleeping_queue_empty				; skip next bit if it is null

	; list is not empty, call the scheduler
	mov 	rax, HPETSleepCounterISR
	call	rax

	; add time to system ms counter
	mov 	rax, 0x100e00000 + 24			; address of system config table ms counter
	mov 	rcx, [rax]						; read value of counter
	inc		rcx								; increase it
	mov 	[rax], rcx						; put it back

.sleeping_queue_empty:
	; send EOI to APIC
	mov	rcx, 0x1b
	rdmsr
	mov	rcx, 0xfffff000
	and	rax, rcx
	add	rax, 0xb0
	mov	[rax], DWORD 0

	; restore trashed registers
	pop		r11
	pop		r10
	pop		r9
	pop		r8
	pop		rdi
	pop		rsi
	pop		rdx
	pop		rcx
	pop		rax

	; return
	iretq


